package com.zj.eswrapper.wrapper;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.zj.eswrapper.enums.BoolEnum;
import com.zj.eswrapper.enums.OperatorEnum;
import com.zj.eswrapper.enums.ScoreModeEnum;
import com.zj.eswrapper.interfaces.impl.AbstractEsQueryWrapper;
import com.zj.eswrapper.utils.CheckUtil;
import com.zj.eswrapper.utils.FieldValueUtil;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * es6 查询条件拼接抽象类
 *
 * @author arthur_zhou
 * @date 2023/12/28 17:10
 */
public abstract class AbstractEsLastQueryWrapper<Field,
        QueryWrapper extends AbstractEsLastQueryWrapper<Field, QueryWrapper, SearchWrapper, AggWrapper>,
        SearchWrapper extends AbstractEsLastSearchWrapper<Field, SearchWrapper>,
        AggWrapper extends AbstractEsLastAggWrapper<Field, AggWrapper, QueryWrapper>>
        extends AbstractEsQueryWrapper<Field, QueryWrapper, SearchWrapper, AggWrapper, QueryBuilder, SearchSourceBuilder, AggregationBuilder> {

    /**
     * @param check 是否校验val的值。true时，val不能为空。关联的EsQueryWrapper全局公用
     */
    public AbstractEsLastQueryWrapper(AtomicBoolean check) {
        super(check);
    }

    public AbstractEsLastQueryWrapper(AbstractEsLastQueryWrapper<?, ?, ?, ?> queryWrapper) {
        super(queryWrapper);
    }

    @Override
    public QueryWrapper term(Field field, Object val, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        TermQueryBuilder builder = QueryBuilders.termQuery(getField(field), val);
        boost(boost, builder);
        return addQuery(builder);
    }


    @Override
    public QueryWrapper terms(Field field, Collection<?> val, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        TermsQueryBuilder builder = QueryBuilders.termsQuery(getField(field), val);
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper match(Field field, Object val, Float boost, OperatorEnum operator) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        MatchQueryBuilder builder = QueryBuilders.matchQuery(getField(field), val);
        Operator o = FieldValueUtil.operator(operator);
        if (Objects.nonNull(o)) {
            builder.operator(o);
        }
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper prefix(Field field, String val, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        PrefixQueryBuilder builder = QueryBuilders.prefixQuery(getField(field), val);
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper matchAll(Float boost) {
        MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper matchPhrase(Field field, String val, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        MatchPhraseQueryBuilder builder = QueryBuilders.matchPhraseQuery(getField(field), val);
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper matchPhrasePrefix(Field field, String val, Float boost, Integer maxExpansions) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        MatchPhrasePrefixQueryBuilder builder = QueryBuilders.matchPhrasePrefixQuery(getField(field), val);
        if (Objects.nonNull(maxExpansions)) {
            builder.maxExpansions(maxExpansions);
        }
        boost(boost, builder);
        return addQuery(builder);
    }

    @SafeVarargs
    @Override
    public final QueryWrapper multiMatch(String val, Float boost, OperatorEnum operator, String minimumShouldMatch, Field... fields) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        String[] fieldNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            fieldNames[i] = getField(fields[i]);
        }
        MultiMatchQueryBuilder builder = QueryBuilders.multiMatchQuery(val, fieldNames).minimumShouldMatch(minimumShouldMatch);
        Operator o = FieldValueUtil.operator(operator);
        if (Objects.nonNull(o)) {
            builder.operator(o);
        }
        boost(boost, builder);
        return addQuery(builder);
    }

    /**
     * 嵌套查询
     *
     * @param path     路径信息
     * @param consumer 函数
     * @return this
     */
    @Override
    public QueryWrapper nested(String path, Consumer<QueryWrapper> consumer, ScoreModeEnum scoreMode, Float boost) {
        return nested(path, consumer, scoreMode, boost, null);
    }

    /**
     * 嵌套查询
     *
     * @param path      路径信息
     * @param consumer  函数
     * @param scoreMode 匹配子对象的分数相关性分数
     * @param boost     权重
     * @param innerHits 返回匹配的嵌套对象
     * @return this
     */
    public QueryWrapper nested(String path, Consumer<QueryWrapper> consumer, ScoreModeEnum scoreMode,
                               Float boost, Function<InnerHitBuilder, InnerHitBuilder> innerHits) {
        QueryWrapper wrapper = createWrapper();
        consumer.accept(wrapper);
        QueryBuilder queryBuilder = wrapper.queryBuilder();
        if (Objects.isNull(queryBuilder)) {
            return child;
        }
        NestedQueryBuilder builder = QueryBuilders.nestedQuery(path, queryBuilder, FieldValueUtil.scoreMode(scoreMode));
        if (Objects.nonNull(innerHits)) {
            builder.innerHit(innerHits.apply(new InnerHitBuilder()));
        }
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper gt(Field field, Object val, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        return range(field, val, null, null, null, boost, null, null, null, null);
    }

    @Override
    public QueryWrapper ge(Field field, Object val, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        return range(field, null, val, null, null, boost, null, null, null, null);
    }

    @Override
    public QueryWrapper lt(Field field, Object val, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        return range(field, null, null, val, null, boost, null, null, null, null);
    }

    @Override
    public QueryWrapper le(Field field, Object val, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        return range(field, null, null, null, val, boost, null, null, null, null);
    }

    @Override
    public QueryWrapper between(Field field, Object ge, Object le, Float boost) {
        if (check.get() && CheckUtil.anyEmpty(ge, le)) {
            return child;
        }
        return range(field, null, ge, null, le, boost, null, null, null, null);
    }

    @Override
    public QueryWrapper range(Field field, String from, String to, Float boost) {
        if (check.get() && CheckUtil.allEmpty(from, to)) {
            return child;
        }
        return range(field, null, null, null, null, boost, from, to, null, null);
    }

    @Override
    public QueryWrapper range(Field field, Object gt, Object gte, Object lt, Object lte, Float boost,
                              String from, String to, String format, String timeZone) {
        RangeQueryBuilder builder = QueryBuilders.rangeQuery(getField(field));
        if (Objects.nonNull(gt)) {
            builder.gt(gt);
        }
        if (Objects.nonNull(gte)) {
            builder.gte(gte);
        }
        if (Objects.nonNull(lt)) {
            builder.lt(lt);
        }
        if (Objects.nonNull(lte)) {
            builder.lte(lte);
        }
        if (Objects.nonNull(from)) {
            builder.from(from);
        }
        if (Objects.nonNull(to)) {
            builder.to(to);
        }
        if (StrUtil.isNotEmpty(format)) {
            builder.format(format);
        }
        if (StrUtil.isNotEmpty(timeZone)) {
            builder.timeZone(timeZone);
        }
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper wildcard(Field field, Float boost, String... val) {
        if (check.get() && CheckUtil.anyEmpty(val)) {
            return child;
        }
        WildcardQueryBuilder builder = QueryBuilders.wildcardQuery(getField(field), String.join("", val));
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper exists(Field field, Float boost) {
        ExistsQueryBuilder builder = QueryBuilders.existsQuery(getField(field));
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryWrapper ids(Collection<String> val, Float boost) {
        if (check.get() && CollectionUtil.isEmpty(val)) {
            return child;
        }
        IdsQueryBuilder builder = QueryBuilders.idsQuery(Objects.nonNull(val) ? val.toArray(new String[0]) : new String[0]);
        boost(boost, builder);
        return addQuery(builder);
    }

    @Override
    public QueryBuilder queryBuilder() {
        if (MapUtil.isEmpty(queryMap)) {
            return null;
        }
        // 当关系是must、should这一个关系且只有一个查询时，不包一层bool
        if (queryMap.size() == 1) {
            List<QueryBuilder> oneRelationList = queryMap.entrySet().stream()
                    .filter(v -> v.getKey() == BoolEnum.MUST || v.getKey() == BoolEnum.SHOULD)
                    .map(Map.Entry::getValue)
                    .findFirst()
                    .orElse(null);
            if (CollectionUtil.isNotEmpty(oneRelationList) && oneRelationList.size() == 1) {
                return oneRelationList.get(0);
            }
        }

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryMap.forEach((bool, queryBuilderList) -> {
            if (CollectionUtil.isEmpty(queryBuilderList)) {
                return;
            }
            for (QueryBuilder builder : queryBuilderList) {
                switch (bool) {
                    case MUST:
                        queryBuilder.must(builder);
                        break;
                    case SHOULD:
                        queryBuilder.should(builder);
                        break;
                    case MUST_NOT:
                        queryBuilder.mustNot(builder);
                        break;
                    case FILTER:
                        queryBuilder.filter(builder);
                        break;
                    default:
                }
            }
        });
        return queryBuilder;
    }

    @Override
    protected SearchSourceBuilder createSearchBuilder() {
        return new SearchSourceBuilder();
    }

    @Override
    public SearchSourceBuilder searchBuilder() {
        addSearch(searchBuilder);
        QueryBuilder queryBuilder = queryBuilder();
        if (Objects.nonNull(queryBuilder)) {
            searchBuilder.query(queryBuilder);
        }
        return searchBuilder;
    }


    /**
     * 赋值权重
     *
     * @param boost        权重
     * @param queryBuilder 条件拼接builder
     */
    private void boost(Float boost, QueryBuilder queryBuilder) {
        if (Objects.nonNull(boost)) {
            queryBuilder.boost(boost);
        }
    }
}
